
import java.io.*;
import java.util.*;

/*
 *  @author  Alpesh Mistry  <apm1dec@gmail.com>
 *  @author  Ankur Kothari  <a.kothari711@gmail.com>
 *  @author  Elman Mansimov <elman.mansimov@gmail.com>
 *  @author  Michelle Pan   <panmiche@gmail.com>
 *  @version 2.0
 *  @since   2012-18-03
 */

public class JShell {

	/**
	 * FileSystem Object initialized to null.
	 * Stores an instance of FileSystem. 
	 */
	protected static FileSystem mockSystem = null;
	
	/**
	 * String object initialized to an empty String.
	 * Stores each word that has been parsed in user input. 
	 */
	private String eachWord = "";
	
	/**
	 * ArrayList Object is created.
	 * Stores all the words that the user entered after the
	 * user input has been parsed.
	 */
	private ArrayList<String> lineList = new ArrayList<String>();
	
	/**
	 * Integer type initialized to 0.
	 * Stores the current index of the array. It has been 
	 * declared in the constructor to change the index of
	 * the array, to skip over something that is not needed. 
	 */
	private int i = 0;

	/**
	 * Returns a String[] array that makes processing user
	 * input easier.
	 * 
	 * This method parses the String argument based on the characters
	 * ' ', '\', '"', '>' and tabs with the help of an ArrayList. This is done 
	 * to account for all kinds of user input. If the double quotes are
	 * uneven in number, then a messaged is printed making the user aware
	 * of the issue. Also String array of length 1 with an empty String 
	 * as its element is returned when this happens or when there is no user input. 
	 *
	 * @param  line A String that stores the user input for processing.
	 * @return A String array that contains the processed String argument.
	 * @see java.lang.String
	 * @see java.util.Arrays
	 * @see	java.lang.Character
	 * @see	java.lang.ArrayList
	 */

	private String[] parseInput(String line) {
		
		char[] charArr = line.trim().toCharArray();
		boolean inQuotes = false;
		String[] inputArray = null;
		String[] emptyStrArr = {""};

		for (i = 0; i < charArr.length; i++)
			if (charArr[i] == '"') 				
				inQuotes = !inQuotes;
			else if (charArr[i] == '\\' && !inQuotes)
				parseForSlash(charArr);
			else if (charArr[i] == '>' && !inQuotes) 
				parseGuillemet(charArr);
			else if (!inQuotes && (charArr[i] == ' ' || charArr[i] == '	'))
				addWord();
			else
				eachWord += charArr[i];
		
		addWord();
		inputArray = new String[lineList.size()];
		inputArray = lineList.toArray(inputArray);

		if (inQuotes){

			System.out.println("Unmatched \".");
			return emptyStrArr;
		}

		return inputArray;
	}

	/**
	 * This method is called when a '\' is found in user input
	 * and appropriate action is taken according to that.
	 * 
	 * This method checks if the next two characters after slash
	 * are spaces or tabs. If both of them are spaces or tabs then eachWord
	 * is added to lineList, else the next character after slash is appended
	 * to eachWord. '\' is never appended to eachWord. 
	 *
	 * @param  charArrGiven A char array that stores the user input for processing.
	 * @see java.lang.String
	 * @see java.util.Arrays
	 * @see	java.lang.Character
	 */

	private void parseForSlash(char[] charArrGiven){

		boolean checkSpace1 = false;
		boolean checkSpace2 = false;
		
		try {
			checkSpace1 = charArrGiven[i+1] == ' ' || charArrGiven[i+1] == '	';
			checkSpace2 = charArrGiven[i+2] == ' ' || charArrGiven[i+2] == '	';

			if (!checkSpace1 || !checkSpace2) {

				eachWord += charArrGiven[i+1];
				i += 1;
			}
		} catch (Exception e) {}
	}

	/**
	 * This method adds the next parsed word in user input to lineList.
	 *  
	 * This method checks if eachWord is not an empty string, and if it isn't
	 * eachWord is added to lineList. After eachWord is added to lineList,
	 * it is initialized to an empty String.
	 * 
	 * @see java.lang.String
	 * @see java.util.ArraysList
	 * @see	java.lang.Character
	 */

	private void addWord(){
		
		if (!eachWord.equals("")) {

			lineList.add(eachWord);
			eachWord = "";
		}
	}

	/**
	 * This method is called when a '>' is found in user input
	 * and appropriate action is taken according to that.
	 * 
	 * This method adds calls addWord before any other actions are taken. 
	 * Adds the '>' to eachWord. Checks if the next characters after '>'
	 * is also '>'. If it is, then '>' is appended to eachWord, otherwise 
	 * eachWord is added to lineList. 
	 *
	 * @param  charArrGiven A char array that stores the user input for processing.
	 * @see java.lang.String
	 * @see java.util.Arrays
	 * @see	java.lang.Character
	 */

	private void parseGuillemet(char[] charArrGiven){
		
		addWord();
		eachWord += charArrGiven[i];

		try {
			if (charArrGiven[i+1] == '>') {

				eachWord += charArrGiven[i+1];
				i += 1;          
			}
		} catch (Exception e) {}

		addWord();
	}
	
	/**
	 * Save the root directory and all its contents and the
	 * location of the working directory before the user quit.
	 * 
	 * This method saves the root directory and all its contents
	 * and the location of the working directory before the user quit
	 * in object array of length 2. The SystemItem objects implement 
	 * serialization for this to work. The object array is saved as a 
	 * file named "filesystem". 
	 *
	 * @see java.lang.String
	 * @see java.util.Arrays
	 * @see	java.io.ObjectOutputStream
	 * @see	java.io.FileOutputStream
	 */

	private void saveFileSystem(String fileName) {
		
		FileOutputStream fos = null;
		ObjectOutputStream out = null;
		Object[] systemSave = {FileSystem.root, FileSystem.currDir.getFullPath() + "/"};
		try
		{
			fos = new FileOutputStream(fileName);
			out = new ObjectOutputStream(fos);
			out.writeObject(systemSave);
			out.close();
		
		} catch(IOException ex) {}
	}
	
	/**
	 * Load the root directory and all its contents and the
	 * location of the working directory before the user quit.
	 * 
	 * This method loads the root directory and all its contents
	 * and the location of the working directory in object array 
	 * of length 2, after the user reopens the JShell. 
	 * The SystemItem objects implement serialization for this 
	 * to work. The object array is saved as a file named "filesystem".
	 * Also changes the working directory to where the user was last
	 * working in. If the file doesn't exist, then the user is using 
	 * the JShell for the first time.
	 *
	 * @throws Exception    If the object array elements are not of the 
	 * 						type Directory and String.
	 * @see java.lang.String
	 * @see java.util.Arrays
	 * @see	java.io.ObjectInputStream
	 * @see	java.io.FileInputStream
	 */

	private void loadFileSystem(String fileName) throws Exception{

		FileInputStream fis = null;
		ObjectInputStream in = null;
		Object[] restore = null;
		String[] currDirPath = {"cd", null};

		try {
			fis = new FileInputStream(fileName);
			in = new ObjectInputStream(fis);
			restore = (Object[]) in.readObject();
			in.close();
		
		} catch(Exception e) {}
		
		if (restore != null) {
			
			FileSystem.root = (Directory) restore[0];
			currDirPath[1] = (String) restore[1];
			mockSystem.selectFunction(currDirPath);
		}
	}

	/**
	 * Reads the user input and executes the commands appropriately.
	 * 
	 * This method parses the user input with the help of parseInput 
	 * method. mockSystem gets an instance of FileSystem 
	 * and find and execute the commands depending on the user input.
	 * This method keeps n asking for user input until the user has 
	 * entered the String "exit". It also prints the output, if there 
	 * is any after command has been executed. 
	 *
	 * @throws Exception    If an instance of FileSystem can't be made.
	 * 					    If the code tries execute a method, that
	 * 						does not exist in the class or is an abstract method
	 *                      in the class.
	 * 						If the code tries to access methods that it
	 * 						is not allowed to. 
	 * 						Method or object that is set private
	 *                      and doesn't give permission to access it.                                     
	 * 						the given method doesn't exist or can't be invoked.
	 * @see java.lang.reflect
	 */

	public void startShell() throws Exception {
		
		mockSystem = FileSystem.getInstance();
		Scanner reader = new Scanner(System.in);		
		String cmdGiven = "start";
		String[] inArr = null;
		String userInput = "";
		String out = "";
		loadFileSystem("userFileSystem");
		while (!cmdGiven.equals("exit")) {

			if (FileSystem.currDir.getParent() != null)
				System.out.print(FileSystem.currDir.getFullPath()	+ "# ");
			else
				System.out.print("/" + "# ");

			userInput = reader.nextLine();
			lineList = new ArrayList<String>();

			try {

				inArr = parseInput(userInput);
				cmdGiven = inArr[0];
				if (cmdGiven.equals("exit") && inArr.length != 1)
					cmdGiven = "reiuytriuyhdfts";

			} catch (Exception e) {

				cmdGiven = "";
			}
			if (!cmdGiven.equals("exit") && !cmdGiven.equals(""))
				out = mockSystem.selectFunction(inArr);
			
			if (!out.equals(""))
				System.out.print(out);

			out = "";
		}
		reader.close();
		saveFileSystem("userFileSystem");
	}

	/**
	 * This the main method from where the code execution begins.
	 * 
	 * Creates a new JShell object and executes an instance method
	 * startShell which has been defined already. It handles the user input
	 * and act accordingly.
	 *
	 * @param args Is an String array that stores the code to be 
	 * 			   executed in this method.
	 * @throws Exception    If an instance of FileSystem can't be made.
	 * 					    If the code tries execute a method, that
	 * 						does not exist in the class or is an abstract method
	 *                      in the class.
	 * 						If the code tries to access methods that it
	 * 						is not allowed to. 
	 * 						Method or object that is set private
	 *                      and doesn't give permission to access it.                                     
	 * 						the given method doesn't exist or can't be invoked.
	 * @see java.lang.reflect
	 */

	public static void main(String[] args) 
		throws 	Exception {

		JShell shellRun = new JShell();
		shellRun.startShell();
	}
}